package com.hbpa.pan.server.modules.user.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hbpa.pan.cache.core.constants.CacheConstants;
import com.hbpa.pan.core.constants.RPanConstants;
import com.hbpa.pan.core.exception.RPanBusinessException;
import com.hbpa.pan.core.response.ResponseCode;
import com.hbpa.pan.core.utils.IdUtil;
import com.hbpa.pan.core.utils.JwtUtil;
import com.hbpa.pan.core.utils.PasswordUtil;
import com.hbpa.pan.server.modules.file.constants.FileConstants;
import com.hbpa.pan.server.modules.file.context.CreateFolderContext;
import com.hbpa.pan.server.modules.file.entity.RPanFile;
import com.hbpa.pan.server.modules.file.entity.RPanUserFile;
import com.hbpa.pan.server.modules.file.service.IUserFileService;
import com.hbpa.pan.server.modules.user.constants.UserConstants;
import com.hbpa.pan.server.modules.user.context.*;
import com.hbpa.pan.server.modules.user.converter.UserConverter;
import com.hbpa.pan.server.modules.user.entity.RPanUser;
import com.hbpa.pan.server.modules.user.service.IUserService;
import com.hbpa.pan.server.modules.user.mapper.RPanUserMapper;
import com.hbpa.pan.server.modules.user.vo.UserInfoVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.Date;
import java.util.Objects;

/**
 *
 */
@Service
public class UserServiceImpl extends ServiceImpl<RPanUserMapper, RPanUser>
        implements IUserService {

    @Autowired
    private UserConverter userConverter;

    @Autowired
    private IUserFileService iuserFileService;

    @Autowired
    private CacheManager cacheManager;


    /**
     * 用户注册的业务实现
     * 需要实现的功能点：
     * 1.注册用户信息
     * 2.创建新用户的根目录信息
     * <p>
     * 需要实现的技术难点：
     * 1.该业务是幂等的
     * 2.要保证用户名全局唯一
     * <p>
     * 实现技术难点的处理方案：
     * 1.幂等性通过数据库表对于用户名字段添加唯一索引，我们上游业务捕获对应的冲突异常，转化返回
     *
     * @param userRegisterContext
     * @return 用户id
     */
    @Override
    public Long register(UserRegisterContext userRegisterContext) {
        //组装 用户实体 -> 用户注册上下文实体
        assembleUserEntity(userRegisterContext);
        //注册用户信息
        doRegister(userRegisterContext);
        //为用户创建根目录
        createUserRootFolder(userRegisterContext);
        return userRegisterContext.getEntity().getUserId();
    }

    /**
     * 用户登录的业务实现
     * <p>
     * 需要实现的功能：
     * 1.用户的登录信息校验
     * 2.为用户登录生成一个具有时效性的accessToken
     * 3.将accessToken缓存起来，去实现单机登录
     *
     * @param userLoginContext
     * @return
     */
    @Override
    public String login(UserLoginContext userLoginContext) {
        //检验用户信息
        checkUserInfo(userLoginContext);
        generateAndSaveAccessToken(userLoginContext);
        return userLoginContext.getAccessToken();
    }

    /**
     * 用户退出登录的业务实现
     * <p>
     * 1.清除用户登录的凭证
     * </p>
     *
     * @param userId
     */
    @Override
    public void exit(Long userId) {
        try {
            Cache redisCache = cacheManager.getCache(CacheConstants.R_PAN_CACHE_NAME);
            redisCache.evict(UserConstants.USER_LOGIN_PREFIX_ + userId);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RPanBusinessException("用户退出登录失败");
        }
    }

    /**
     * 校验用户名的业务实现
     *
     * @param checkUsernameContext
     * @return
     */
    @Override
    public String checkUsername(CheckUsernameContext checkUsernameContext) {
        String username = checkUsernameContext.getUsername();
        String question = baseMapper.selectQuestionByUsername(username);
        if (StringUtils.isBlank(question)) {
            throw new RPanBusinessException("用户不存在");
        }
        return question;
    }

    /**
     * 校验密保答案业务实现
     *
     * @param checkAnswerContext 校验密保答案参数上下文实体
     * @return 用于接下来修改密码的临时token
     */
    @Override
    public String checkAnswer(CheckAnswerContext checkAnswerContext) {
        LambdaQueryWrapper<RPanUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(true, RPanUser::getUsername, checkAnswerContext.getUsername())
                .eq(true, RPanUser::getQuestion, checkAnswerContext.getQuestion())
                .eq(true, RPanUser::getAnswer, checkAnswerContext.getAnswer());
        int count = this.count(queryWrapper);
        if (0 == count) {
            throw new RPanBusinessException("密保答案错误");
        }
        //因为token是临时的，所以不需要保存
        String temporaryToken = generateCheckAnswerToken(checkAnswerContext);
        return temporaryToken;
    }

    /**
     * 重置用户密码业务实现
     * 1.校验重置密码的token
     * 2.重置密码
     *
     * @param resetPasswordContext
     */
    @Override
    public void resetPassword(ResetPasswordContext resetPasswordContext) {
        //校验忘记密码的token
        checkForgetPasswordToken(resetPasswordContext);
        checkAndResetPassword(resetPasswordContext);

    }

    /**
     * 用户在线修改密码业务实现
     * 1.校验旧密码
     * 2.重置新密码
     * 3.推出当前登录状态
     *
     * @param changePasswordContext
     */
    @Override
    public void changePassword(ChangePasswordContext changePasswordContext) {
        checkOldPassword(changePasswordContext);
        doChangePassword(changePasswordContext);
        exitLoginStatue(changePasswordContext);
    }

    /**
     * 查询在线用户的基本信息
     * 1.查询用户的基本信息实体
     * 2.查询用户的根文件夹信息
     * 3.拼装VO对象返回
     *
     * @param userId
     * @return
     */
    @Override
    public UserInfoVO info(Long userId) {
        RPanUser rPanUser = getById(userId);
        if (Objects.isNull(rPanUser)) {
            throw new RPanBusinessException("用户不存在");
        }
        RPanUserFile rPanUserFile = getUserRootFileInfo(userId);
        if (Objects.isNull(rPanUser)) {
            throw new RPanBusinessException("查询用户根文件夹信息失败");
        }

        return userConverter.assembleUserInfoVO(rPanUser, rPanUserFile);
    }


    /********************************************private********************************************/

    /**
     * 获取用户根文件夹信息
     *
     * @param userId
     * @return
     */
    private RPanUserFile getUserRootFileInfo(Long userId) {
        return iuserFileService.getUserRootFile(userId);
    }

    /**
     * 校验用户的旧密码
     * 该步骤会查询并封装用户的实体信息到上下文对象中
     *
     * @param changePasswordContext
     */
    private void checkOldPassword(ChangePasswordContext changePasswordContext) {
        Long userId = changePasswordContext.getUserId();
        String oldPassword = changePasswordContext.getOldPassword();

        RPanUser entity = getById(userId);
        if (Objects.isNull(entity)) {
            throw new RPanBusinessException("用户信息不存在");
        }
        changePasswordContext.setEntity(entity);
        String encOldPassword = PasswordUtil.encryptPassword(entity.getSalt(), oldPassword);
        String dbOldPassword = entity.getPassword();
        if (!Objects.equals(dbOldPassword, encOldPassword)) {
            throw new RPanBusinessException("旧密码错误");
        }
    }

    /**
     * 修改新密码
     *
     * @param changePasswordContext
     */
    private void doChangePassword(ChangePasswordContext changePasswordContext) {
        String newPassword = changePasswordContext.getNewPassword();
        RPanUser entity = changePasswordContext.getEntity();
        String salt = entity.getSalt();
        String encNewPassword = PasswordUtil.encryptPassword(salt, newPassword);
        entity.setPassword(encNewPassword);

        if (!updateById(entity)) {
            throw new RPanBusinessException("修改用户密码失败");
        }
    }

    /**
     * 退出用户的登录状态
     *
     * @param changePasswordContext
     */
    private void exitLoginStatue(ChangePasswordContext changePasswordContext) {
        exit(changePasswordContext.getUserId());
    }

    /**
     * 校验用户信息并重置用户密码
     *
     * @param resetPasswordContext
     */
    private void checkAndResetPassword(ResetPasswordContext resetPasswordContext) {
        String username = resetPasswordContext.getUsername();
        String password = resetPasswordContext.getPassword();
        RPanUser entity = getRPanUserByUsername(username);
        if (Objects.isNull(entity)) {
            throw new RPanBusinessException("用户信息不存在");
        }
        String newPassword = PasswordUtil.encryptPassword(entity.getSalt(), password);
        entity.setPassword(newPassword);
        entity.setUpdateTime(new Date());
        if (!updateById(entity)) {
            throw new RPanBusinessException("重置用户密码失败");
        }
    }

    /**
     * 验证忘记密码的token是否有效
     *
     * @param resetPasswordContext
     */
    private void checkForgetPasswordToken(ResetPasswordContext resetPasswordContext) {
        String token = resetPasswordContext.getToken();
        Object value = JwtUtil.analyzeToken(token, UserConstants.FORGET_USERNAME);
        if (Objects.isNull(value)) {
            throw new RPanBusinessException(ResponseCode.TOKEN_EXPIRE);
        }
        if (!Objects.equals(value, resetPasswordContext.getUsername())) {
            throw new RPanBusinessException("token错误或过期");
        }
    }

    /**
     * 生成用户忘记密码-校验密保答案通过的临时token
     *
     * @return
     */
    private String generateCheckAnswerToken(CheckAnswerContext checkAnswerContext) {
        String temporaryToken = JwtUtil.generateToken(checkAnswerContext.getUsername(), UserConstants.FORGET_USERNAME, checkAnswerContext.getUsername(), UserConstants.FIVE_MINUTES_MS_LONG);
        return temporaryToken;
    }

    /**
     * 生成并保存登录之后的凭证
     *
     * @param userLoginContext
     * @return
     */
    private void generateAndSaveAccessToken(UserLoginContext userLoginContext) {
        RPanUser entity = userLoginContext.getEntity();
        //生成accessToken
        String accessToken = JwtUtil.generateToken(entity.getUsername(), UserConstants.LOGIN_USER_ID, entity.getUserId(), UserConstants.ONE_DAY_MS_LONG);
        //获取缓存并将accessToken存入redis中
        Cache redisCache = cacheManager.getCache(CacheConstants.R_PAN_CACHE_NAME);
        redisCache.put(UserConstants.USER_LOGIN_PREFIX_ + entity.getUserId(), accessToken);
        //将accessToken信息更新到上下文对象
        userLoginContext.setAccessToken(accessToken);
    }

    /**
     * 检验用户名和密码
     *
     * @param userLoginContext
     */
    private void checkUserInfo(UserLoginContext userLoginContext) {
        //获取用户名和密码
        String username = userLoginContext.getUsername();
        String password = userLoginContext.getPassword();
        //查询用户信息
        RPanUser entity = getRPanUserByUsername(username);
        if (Objects.isNull(entity)) {
            throw new RPanBusinessException("用户名不存在!");
        }
        String salt = entity.getSalt();
        //获取加密密码
        String encPassword = PasswordUtil.encryptPassword(salt, password);
        String dbPassword = entity.getPassword();
        if (!StringUtils.equals(encPassword, dbPassword)) {
            throw new RPanBusinessException("用户密码错误");
        }
        //保存实体信息到上下文对象
        userLoginContext.setEntity(entity);
    }

    /**
     * 通过用户名获取用户实体信息
     *
     * @param username 用户名
     * @return 用户实体
     */
    private RPanUser getRPanUserByUsername(String username) {
        LambdaQueryWrapper<RPanUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(true, RPanUser::getUsername, username);
        return this.getOne(queryWrapper);
    }

    /**
     * 实体转化
     * 由上下文实体转换为用户实体，封装进上下文
     *
     * @param userRegisterContext
     * @return
     */
    private void assembleUserEntity(UserRegisterContext userRegisterContext) {
        RPanUser entity = userConverter.userRegisterContext2RPanUser(userRegisterContext);
        String salt = PasswordUtil.getSalt();
        //对用户密码进行摘要算法加密
        String dbPassword = PasswordUtil.encryptPassword(salt, userRegisterContext.getPassword());
        //设置唯一的用户id(雪花算法)
        entity.setUserId(IdUtil.get());
        entity.setSalt(salt);
        entity.setPassword(dbPassword);
        Date now = new Date();
        entity.setCreateTime(now);
        entity.setUpdateTime(now);
        userRegisterContext.setEntity(entity);
    }

    /**
     * 实现注册用户业务
     * 需要捕获数据库的唯一索引冲突异常，来实现全局用户名称唯一
     *
     * @param userRegisterContext
     */
    private void doRegister(UserRegisterContext userRegisterContext) {
        RPanUser entity = userRegisterContext.getEntity();
        if (Objects.nonNull(entity)) {
            try {
                if (!this.save(entity)) {
                    throw new RPanBusinessException("用户注册失败");
                }
            } catch (DuplicateKeyException duplicateKeyException) {
                throw new RPanBusinessException("用户名已存在");
            }
            return;
        }
        throw new RPanBusinessException(ResponseCode.ERROR);
    }

    /**
     * 初始化用户的根目录信息
     *
     * @param userRegisterContext
     */
    private void createUserRootFolder(UserRegisterContext userRegisterContext) {
        //创建目录上下文对象
        CreateFolderContext createFolderContext = new CreateFolderContext();
        createFolderContext.setParentId(FileConstants.TOP_PARENT_ID);
        createFolderContext.setUserId(userRegisterContext.getEntity().getUserId());
        createFolderContext.setFolderName(FileConstants.CN_ALL_FILE_STR);
        iuserFileService.createFolder(createFolderContext);
    }

}




